package interfaze;

import java.awt.Font;
import java.awt.Image;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.util.ArrayList;

import javax.swing.ImageIcon;
import javax.swing.JLabel;
import javax.swing.JPanel;

import constants.BrickColor;
import constants.PercentNumber;
import constants.WallType;

import entities.AmmunitionCounter;
import entities.Ball;
import entities.Bat;
import entities.Bonus;
import entities.Brick;
import entities.Level;
import entities.Life;
import entities.Rocket;
import entities.Score;
import entities.Wall;

import logic.BBDataSaver;
import logic.BatLimiterThread;
import logic.CollisionThread;
import logic.MovementThread;
import logic.NoSaveException;
import logic.Utilities;

/** Brick Breaker's Gaming Panel class.
 * @author Team 1
 *
 */
@SuppressWarnings("serial")
public class BrickBreakerGamingPanel extends JPanel implements KeyListener, FocusListener {
	
	private final int wallHeight = 30;			//The y where the upper walls are going to be located.
	private int columnNumber;					//The number of the columns for the bricks.
	private int rowNumber;						//The number of the row for the bricks.
	private int livesNumber;					//The lives number for the player.
	private int ammoNumber;						//The number of ammunition of the bat.
	private int batSpeed;						//The bat's speed.
	private int ballSpeed;						//The balls' speed.
	private int bonusSpeed;						//The bonus' speed.
	private int brickLives;						//The number of lives for the bricks.
	private int levelNumber;					//The number of the actual level.
	private long scoreNumber;					//The number of the achieved points number.
	private PercentNumber percent;				//The percent for the creation of bonuses.
	private boolean isApressed = false;			//A flag that controls when the A key is pressed.
	private boolean isDpressed = false;			//A flag that controls when the D key is pressed.
	public SuperArcade2in1 window;				//The window where the is panel is added.
	public JLabel background = new JLabel();	//The background of the panel.
	public Bat bat;								//The player.							
	public AmmunitionCounter counter;			//The ammunition counter located at the bottom of the player.
	public Life lives;							//The label that displays the lives number.
	public Score points;						//The label that displays the points number.
	public Level level;							//The label that displays the actual level.
	public ArrayList<Ball> ballList = new ArrayList<Ball>();	//The list of all the balls located on the playground.
	public ArrayList<Wall> wallList = new ArrayList<Wall>();	//The list of all the walls located on the playground.
	public ArrayList<Brick> brickList = new ArrayList<Brick>();	//The list of all the bricks located on the playground.
	public ArrayList<Rocket> rocketList = new ArrayList<Rocket>();	//The list of all the rockets located on the playground.
	public ArrayList<Bonus> bonusList = new ArrayList<Bonus>();		//The list of all the bonus located on the playground.
	public ArrayList<MovementThread> threadList = new ArrayList<MovementThread>();	//The list of all the threads for the movement.
	public CollisionThread collision;	//The thread that control all the logic related to the collisions.
	public BatLimiterThread limiterThread;		//The thread that limits the bat's and ball's movements.
	private Font font;							//The font used for all the text labels.
	
	
	
	/** The BrickBreakerGamingPanel's construction.
	 * @param window : The JFrame in which this panel is being added.
	 * @param load : A flag that represents if the panel need to read from a save data file or not.	
	 * @param livesNumber : The initial lives number.
	 * @param scoreNumber : The initial points number.
	 * @param ammoNumber : The initial ammo number.
	 * @param rowNumber	: The row number for all the levels.
	 * @param columnNumber : The column number for all the levels.
	 * @param ballSpeed	: The balls' speed for all the levels.
	 * @param batSpeed : The bat's speed for all the levels.
	 * @param bonusSpeed : The bonuses' speed for all the levels.
	 * @param percent : The percent for the creation of the bonuses for all the levels.
	 * @param brickLives : The brick's lives for the current level.
	 * @param levelNumber : The number displayed for level.
	 */
	public BrickBreakerGamingPanel(SuperArcade2in1 window, boolean load, int livesNumber, long scoreNumber, int ammoNumber, int rowNumber,
			int columnNumber, int ballSpeed, int batSpeed, int bonusSpeed, PercentNumber percent, int brickLives, int levelNumber) {
		//Initializes the variables.
		this.window = window;
		this.columnNumber = columnNumber;
		this.rowNumber = rowNumber;
		this.livesNumber = livesNumber;
		this.ammoNumber = ammoNumber;
		this.batSpeed = batSpeed;
		this.ballSpeed = ballSpeed;
		this.bonusSpeed = bonusSpeed;
		this.levelNumber = levelNumber;
		this.scoreNumber = scoreNumber;
		this.percent = percent;
		this.brickLives = brickLives;
		
		//Sets the panel's layout to null in order to work with absolute coordinates.
		this.setLayout(null);
		
		//Request the focus in order make the key listener work.
		this.setFocusable(true);
		this.requestFocus();
		
		//Sets the font for all the text labels.
		font = new Font("Helvetica", Font.PLAIN, Math.round(12 * window.xScale));
		
		//Setting Background
		setBackGround();	
		
		//Adding Components
		ArrayList<Object> entityList = new ArrayList<>();
		try {
			//Opens the save data file.
			entityList = BBDataSaver.load(window.userName, window.xScale, window.yScale);
		} catch (NoSaveException e) {
			//If the file doesn't exit sets the load flag to false.
			load = false;
		}
		if(!load) {
			//Sets the Components by default if the save data doesn't exit or the flag is already false.
			setComponents();
		} else {
			//Loads entities and configuration from saved data file.
			for(int i = 0; i < entityList.size(); i++) {
				Object list = entityList.get(i);
				if (list instanceof Bat) {
					bat = (Bat) list;
					MovementThread thread = new MovementThread((Bat) list);
					threadList.add(0, thread);
				}else if (list instanceof Brick) {
					brickList.add((Brick) list);
				} else if (list instanceof Bonus) {
					bonusList.add((Bonus) list);
					MovementThread thread = new MovementThread((Bonus) list);
					threadList.add(thread);
				} else if (list instanceof Wall) {
					wallList.add((Wall) list);
				} else if (list instanceof Ball) {
					ballList.add((Ball) list);
					MovementThread thread = new MovementThread((Ball) list);
					threadList.add(thread);
				}else if(list instanceof Rocket) {
					rocketList.add((Rocket) list);
					MovementThread thread = new MovementThread((Rocket) list);
					threadList.add(thread);
				} else if (list instanceof Score) {
					((Score) list).setFont(font);
					points = (Score) list;
				} else if (list instanceof Life) {
					((Life) list).setFont(font);
					lives = (Life) list;
				} else if(list instanceof Level) {
					((Level) list).setFont(font);
					level = (Level) list;
				}else if(list instanceof AmmunitionCounter) {
					((AmmunitionCounter) list).setFont(font);
					counter = (AmmunitionCounter) list;	
				} else if(list instanceof Object[]) {
					this.ballSpeed = ((Integer)(((Object[]) list)[0])).intValue();
					this.batSpeed = ((Integer)((Object[]) list)[1]).intValue();
					this.bonusSpeed = ((Integer)((Object[]) list)[2]).intValue();
					this.percent = (PercentNumber)((Object[]) list)[3];
					this.rowNumber = ((Integer)((Object[]) list)[4]).intValue();
					this.columnNumber = ((Integer)((Object[]) list)[5]).intValue();
				}
			}
		}
		
		//Adds all the components to the panel.
		this.addComponents();
		
		//Sets the listeners.
		this.addKeyListener(this);
		this.addFocusListener(this);
		
		//Starts the threads of collision, movement limiter and all the movements.
		collision = new CollisionThread(ballList, brickList, bonusList, rocketList, wallList, threadList, limiterThread, bat, this, window,
				this.ballSpeed, this.bonusSpeed, this.batSpeed, this.percent, lives, points, counter, level, this.brickLives,
				this.columnNumber, this.rowNumber);
		collision.start();
		
		limiterThread = new BatLimiterThread(bat, window.width, wallList, ballList, counter, 
				new Boolean(isApressed), new Boolean(isDpressed));
		limiterThread.start();
		
		for(int i = 0; i < threadList.size(); i++) {
			threadList.get(i).start();
		}
	}
	
	/** Loads the background resource and resizes proportionally if the windows resolution is not
	 * 1280x720. Then sets that image to the background label with its boundaries.
	 */
	private void setBackGround() {
		Image image = logic.Utilities.loadImage("res/background.png");
		if(window.xScale != 1 || window.yScale != 1) {
			image = Utilities.resizeImage(window.xScale, window.yScale, "res/background.png");
		}
		background.setIcon(new ImageIcon(image));
		background.setBounds(0, 0, image.getWidth(null), image.getHeight(null));
	}
	
	/** Sets the location of all the components of the panel. Bats, Ball, Bricks, Bonus, and so on.
	 */
	private void setComponents() {
		wallList.add(new Wall(0, Math.round(wallHeight * window.yScale), WallType.UPPERLEFTWALL, window.xScale, window.yScale));
		wallList.add(new Wall(Wall.getWidth(WallType.UPPERLEFTWALL, window.xScale), Math.round(wallHeight * window.yScale),
				WallType.UPWALL, window.xScale, window.yScale));
		wallList.add(new Wall(Wall.getWidth(WallType.UPPERLEFTWALL, window.xScale) + Wall.getWidth(WallType.UPWALL, window.xScale), 
				Math.round(wallHeight * window.yScale), WallType.UPPERRIGHTWALL, window.xScale, window.yScale));
		wallList.add(new Wall(0, wallList.get(0).getY() + Wall.getHeight(WallType.UPPERLEFTWALL, window.yScale),
				WallType.LEFTWALL, window.xScale, window.yScale));
		wallList.add(new Wall(Wall.getWidth(WallType.UPPERLEFTWALL, window.xScale) + Wall.getWidth(WallType.UPWALL, window.xScale),
				wallList.get(0).getY() + Wall.getHeight(WallType.UPPERLEFTWALL, window.yScale),
				WallType.RIGHTWALL, window.xScale, window.yScale));
		
		bat = new Bat((window.width / 2) - (Bat.getWidth(window.xScale) / 2), 
				Math.round(window.height - (Bat.getHeight(window.yScale) + (75 * window.yScale))),
				window.xScale, window.yScale);
		bat.setAmmo(ammoNumber);
		threadList.add(new MovementThread(bat));
		
		level = new Level(window.width / 2 - Math.round(55 * window.xScale), Math.round(15 * window.yScale), window.xScale, window.yScale);
		level.setFont(font);
		level.setLevel(levelNumber);
		
		//Sets the brick.
		setBricksAlign();
		
		ballList.add(new Ball(Math.round((window.width / 2) - (Ball.getWidth(window.xScale) / 2)),
				bat.getY() - Ball.getHeight(window.yScale), window.xScale, window.xScale));
		
		lives = new Life(Math.round(15 * window.xScale), Math.round(15 * window.yScale), window.xScale, window.yScale);
		lives.setFont(font);
		lives.setLives(livesNumber);
		
		points = new Score(window.width - Math.round(185 * window.xScale), Math.round(15 * window.xScale), window.xScale, window.yScale);
		points.setFont(font);
		points.setPoints(scoreNumber);
		
		counter = new AmmunitionCounter((bat.getX() + bat.getWidth() / 2) - Math.round(9 * window.xScale),
				bat.getY() + bat.getHeight(), window.xScale, window.yScale, bat.getAmmo());
	}
	
	/** Sets the brick align to center depending on the column number.
	 */
	private void setBricksAlign() {
		int brickColumnWidth = Brick.getWidth(window.xScale) * columnNumber + Math.round(10 * window.xScale) * (columnNumber - 1);
		int x = window.width / 2 - brickColumnWidth / 2;
		int y = wallList.get(1).getY() + Wall.getHeight(WallType.UPWALL, window.yScale) + Math.round(20 * window.yScale);
		for(int j = 0; j < rowNumber; j ++) {
			for(int i = 0; i < columnNumber; i++) {
				brickList.add(new Brick(x, y, BrickColor.BLUE, level.getLevel(), window.xScale, window.xScale));
				x += Brick.getWidth(window.xScale) + Math.round(10 * window.xScale);	
			}
			x = window.width / 2 - brickColumnWidth / 2;
			y += Brick.getHeight(window.yScale) + Math.round(5 * window.yScale);
		}
	}
	
	/** Adds all the components to the panel.
	 */
	private void addComponents() {
		this.add(background, -1);
		this.add(bat, 0);
		this.add(lives, 0);
		this.add(points, 0);
		this.add(level, 0);
		this.add(counter, 0);
		for(int i = 0; i < wallList.size(); i++) {
			this.add(wallList.get(i), 0);
		}
		for(int i = 0; i < ballList.size(); i++) {
			this.add(ballList.get(i), 0);
		}
		for(int i = 0; i < brickList.size(); i++) {
			this.add(brickList.get(i), 0);
		}
		for(int i = 0; i < rocketList.size(); i++) {
			this.add(rocketList.get(i), 0);
		}
		for(int i = 0; i < bonusList.size(); i++) {
			this.add(bonusList.get(i), 0);
		}
	}
	
	@Override
	public void keyPressed(KeyEvent e) {
		MovementThread thread = null;
		switch(e.getKeyCode()) {
		case KeyEvent.VK_A:
			//If key A is pressed generates the movement and movement limiter threads to the left.
			isApressed = true;
			bat.setDX(Math.round(-1 * batSpeed * window.xScale));
			limiterThread.setIsApressed(true);
			if(!limiterThread.isAlive()) {
				limiterThread = new BatLimiterThread(bat, window.width, wallList, ballList, counter,
						new Boolean(true), new Boolean(isDpressed));
				limiterThread.start();
			}
			if(!threadList.get(0).isAlive()) {
				threadList.remove(0);
				thread = new MovementThread(bat);
				thread.start();
				threadList.add(0, thread);
			}
			break;
		case KeyEvent.VK_D:
			//If key D is pressed generates the movement and movement limiter threads to the right.
			isDpressed = true;
			bat.setDX(Math.round(batSpeed * window.xScale));
			limiterThread.setIsDpressed(true);
			if(!limiterThread.isAlive()) {
				limiterThread = new BatLimiterThread(bat, window.width, wallList, ballList, counter, 
						new Boolean(isApressed), new Boolean(true));
				limiterThread.start();
			}
			if(!threadList.get(0).isAlive()) {
				threadList.remove(0);
				thread = new MovementThread(bat);
				thread.start();
				threadList.add(0, thread);
			}
			break;
		case KeyEvent.VK_S:
			/*If the S key is pressed and the player has ammo generates a rocket, subtracting 1 one bullet, with its movement thread.
			 *and if collision thread is not alive starts a new one.
			 */
			if(bat.getCanShoot()) {
				Rocket rocket = new Rocket(bat.getX() + (bat.getWidth() / 2 - Rocket.getWidth(window.xScale) / 2), bat.getY(), 
						0, Math.round(-6 * window.yScale), window.xScale, window.yScale);
				rocketList.add(rocket);
				this.add(rocket, 0);
				if(!collision.isAlive()) {
					collision = new CollisionThread(ballList, brickList, bonusList, rocketList, wallList, threadList, limiterThread,
							bat, this, window, ballSpeed, bonusSpeed, batSpeed, percent, lives, points, counter, level,
							brickLives, columnNumber, rowNumber);
					collision.start();
				}
				thread = new MovementThread(rocket);
				thread.start();
				threadList.add(thread);
				bat.setAmmo(bat.getAmmo() - 1);
				counter.setAmmoCounter(bat.getAmmo());
			}
			break;
		case KeyEvent.VK_SPACE:
			/*When pressing Space key if there's only one ball and if it can't move starts its movement.
			 *if the collision thread is not alive starts a new one.
			 */
			if(!ballList.get(0).canMove()) {
				if(isApressed) {
					ballList.get(0).setDX(Math.round(-ballSpeed * window.xScale));
				} else {
					ballList.get(0).setDX(Math.round(ballSpeed * window.xScale));
				}
				ballList.get(0).setDY(Math.round(-ballSpeed * window.yScale));
				if(!collision.isAlive()) {
					collision = new CollisionThread(ballList, brickList, bonusList, rocketList, wallList, threadList, limiterThread,
							bat, this, window, ballSpeed, bonusSpeed, batSpeed, percent, lives, points, counter, level, 
							brickLives, columnNumber, rowNumber);
					collision.start();
				}
				thread = new MovementThread(ballList.get(0));
				thread.start();
				threadList.add(thread);
			}
			break;
		}
	}

	@Override
	public void keyReleased(KeyEvent e) {
		switch(e.getKeyCode()) {
		case KeyEvent.VK_ESCAPE:
			//Stops all the threads and saves all the entities and configuration into the save data file.
			if(brickList.size() != 0 && lives.getLives() > -1) {
				for(int i = 0; i < threadList.size(); i++) {
					threadList.get(i).interrupt();
				}
				if(collision.isAlive()) {
					collision.interrupt();
				}
				threadList.clear();
				if(limiterThread.isAlive()) {
					limiterThread.interrupt();
				}
				Object[] config = new Object[6];
				config[0] = ballSpeed;
				config[1] = batSpeed;
				config[2] = bonusSpeed;
				config[3] = percent;
				config[4] = rowNumber;
				config[5] = columnNumber;
				BBDataSaver.write(window.userName, bat, bonusList, brickList, ballList, rocketList, wallList, points, lives,
						level, counter, config, (float)Math.pow(window.xScale, -1), (float)Math.pow(window.yScale, -1));
				//Redirects to the pause menu.
				window.add(new BrickBreakerPausePanel(window));
				this.setVisible(false);
				window.remove(this);
			}
			break;
		case KeyEvent.VK_A:
			//Sets off the A key flag and if D key is also released stops the bat's movement and limiter.
			isApressed = false;
			limiterThread.setIsApressed(false);
			if(!isDpressed) {
				bat.setDX(0);
			} else {
				bat.setDX(Math.round(batSpeed * window.xScale));
			}
			break;
		case KeyEvent.VK_D:
			//Sets off the D key flag and if A key is also released stops the bat's movement and limiter.
			isDpressed = false;
			limiterThread.setIsDpressed(false);
			if(!isApressed) {
				bat.setDX(0);
			} else {
				bat.setDX(Math.round(-batSpeed * window.xScale));
			}
			break;
		}
	}

	@Override
	public void keyTyped(KeyEvent e) {
		
	}

	@Override
	public void focusGained(FocusEvent e) {
		
	}

	@Override
	public void focusLost(FocusEvent e) {
		//Request the crazy focus.
		this.requestFocus();
	}
}
